import { expect } from 'chai';
import { getErrObj, getFirstErrMsg, validate } from '../src/validator.js';

describe('validator unit test', function () {
    it('should return empty array', function () {
        expect(validate()).to.be.eql([]);
    });

    it('should return required msg', function () {
        const raw = {};
        const rules = [
            {
                id: 'foo',
                rules: [{ required: true, msg: 'foo is required' }],
            },
        ];
        const expected = [{ id: 'foo', ok: false, msg: 'foo is required' }];

        expect(validate(raw, rules)).to.be.eql(expected);
    });

    it('should return ok', function () {
        const raw = { foo: 1 };
        const rules = [
            {
                id: 'foo',
                rules: [{ required: true, msg: 'foo is required' }],
            },
        ];
        const expected = [{ id: 'foo', ok: true }];

        expect(validate(raw, rules)).to.be.eql(expected);
    });

    it('should return phone is invalid', function () {
        const raw = {
            phone: '1',
        };
        const rules = [
            {
                id: 'phone',
                rules: [
                    { required: true, msg: 'phone is required' },
                    { phone: true, msg: 'phone is invalid' },
                ],
            },
        ];
        const expected = [{ id: 'phone', ok: false, msg: 'phone is invalid' }];

        expect(validate(raw, rules)).to.be.eql(expected);
    });

    it('should return phone is invalid', function () {
        const raw = {
            phone: '158222222223',
        };
        const rules = [
            {
                id: 'phone',
                rules: [
                    { required: true, msg: 'phone is required' },
                    { phone: true, msg: 'phone is invalid' },
                ],
            },
        ];
        const expected = [{ id: 'phone', ok: false, msg: 'phone is invalid' }];

        expect(validate(raw, rules)).to.be.eql(expected);
    });

    it('should return phone is valid', function () {
        const raw = {
            phone: '15822222222',
        };
        const rules = [
            {
                id: 'phone',
                rules: [
                    { required: true, msg: 'phone is required' },
                    { phone: true, msg: 'phone is invalid' },
                ],
            },
        ];
        const expected = [{ id: 'phone', ok: true }];

        expect(validate(raw, rules)).to.be.eql(expected);
    });

    it('should return name is required', function () {
        const raw = {
            phone: '15822222222',
        };
        const rules = [
            {
                id: 'phone',
                rules: [
                    { required: true, msg: 'phone is required' },
                    { phone: true, msg: 'phone is invalid' },
                ],
            },
            {
                id: 'name',
                rules: [{ required: true, msg: 'name is required' }],
            },
        ];
        const expected = [
            { id: 'phone', ok: true },
            { id: 'name', ok: false, msg: 'name is required' },
        ];

        expect(validate(raw, rules)).to.be.eql(expected);
    });

    it('should return name too short', function () {
        const raw = {
            phone: '15822222222',
            name: 'hello',
        };
        const rules = [
            {
                id: 'phone',
                rules: [
                    { required: true, msg: 'phone is required' },
                    { phone: true, msg: 'phone is invalid' },
                ],
            },
            {
                id: 'name',
                rules: [
                    { required: true, msg: 'name is required' },
                    { minlength: 6, msg: 'name too short' },
                ],
            },
        ];
        const expected = [
            { id: 'phone', ok: true },
            { id: 'name', ok: false, msg: 'name too short' },
        ];

        expect(validate(raw, rules)).to.be.eql(expected);
    });

    it('should return name ok', function () {
        const raw = {
            phone: '15822222222',
            name: 'hello world',
        };
        const rules = [
            {
                id: 'phone',
                rules: [
                    { required: true, msg: 'phone is required' },
                    { phone: true, msg: 'phone is invalid' },
                ],
            },
            {
                id: 'name',
                rules: [
                    { required: true, msg: 'name is required' },
                    { minlength: 6, msg: 'name too short' },
                ],
            },
        ];
        const expected = [
            { id: 'phone', ok: true },
            { id: 'name', ok: true },
        ];

        expect(validate(raw, rules)).to.be.eql(expected);
    });

    it('should return name ok with filter', function () {
        const raw = {
            type: '2',
            name: '123',
        };
        const rules = [
            {
                id: 'type',
                rules: [{ required: true, msg: 'type is required' }],
            },
            {
                id: 'name',
                rules: [
                    { required: true, msg: 'name is required' },
                    {
                        filter: (item) => item.type === '1',
                        minlength: 6,
                        msg: 'name too short',
                    },
                ],
            },
        ];
        const expected = [
            { id: 'type', ok: true },
            { id: 'name', ok: true },
        ];

        expect(validate(raw, rules)).to.be.eql(expected);
    });

    it('should return name too short with filter', function () {
        const raw = {
            type: '1',
            name: '123',
        };
        const rules = [
            {
                id: 'type',
                rules: [{ required: true, msg: 'type is required' }],
            },
            {
                id: 'name',
                rules: [
                    { required: true, msg: 'name is required' },
                    {
                        filter: (item) => item.type === '1',
                        minlength: 6,
                        msg: 'name too short',
                    },
                ],
            },
        ];
        const expected = [
            { id: 'type', ok: true },
            { id: 'name', ok: false, msg: 'name too short' },
        ];

        expect(validate(raw, rules)).to.be.eql(expected);
    });

    it('should return ok with field-level filter', function () {
        const raw = {
            type: '1',
            name: '123',
        };
        const rules = [
            {
                id: 'type',
                rules: [{ required: true, msg: 'type is required' }],
            },
            {
                id: 'name',
                filter: (item) => item.type === '2',
                rules: [
                    { required: true, msg: 'name is required' },
                    {
                        minlength: 6,
                        msg: 'name too short',
                    },
                ],
            },
        ];
        const expected = [{ id: 'type', ok: true }];

        expect(validate(raw, rules)).to.be.eql(expected);
    });

    it('should return too small for min rule', function () {
        const raw = {
            age: 14,
        };

        const rules = [
            {
                id: 'age',
                rules: [
                    {
                        min: 18,
                        msg: 'too young',
                    },
                ],
            },
        ];

        const expected = [{ id: 'age', ok: false, msg: 'too young' }];

        expect(validate(raw, rules)).to.be.eql(expected);
    });

    it('should return ok for min rule', function () {
        const raw = {
            age: 18,
        };

        const rules = [
            {
                id: 'age',
                rules: [
                    {
                        min: 18,
                        msg: 'too young',
                    },
                ],
            },
        ];

        const expected = [{ id: 'age', ok: true }];

        expect(validate(raw, rules)).to.be.eql(expected);
    });

    it('should return too old for max rule', function () {
        const raw = {
            age: 19,
        };

        const rules = [
            {
                id: 'age',
                rules: [
                    {
                        max: 18,
                        msg: 'too old',
                    },
                ],
            },
        ];

        const expected = [{ id: 'age', ok: false, msg: 'too old' }];

        expect(validate(raw, rules)).to.be.eql(expected);
    });

    it('should return ok for max rule', function () {
        const raw = {
            age: 17,
        };

        const rules = [
            {
                id: 'age',
                rules: [
                    {
                        max: 18,
                        msg: 'too old',
                    },
                ],
            },
        ];

        const expected = [{ id: 'age', ok: true }];

        expect(validate(raw, rules)).to.be.eql(expected);
    });

    it('should return ok with field-level filter', function () {
        const raw = {
            type: '2',
            name: '123',
        };
        const rules = [
            {
                id: 'type',
                rules: [{ required: true, msg: 'type is required' }],
            },
            {
                id: 'name',
                filter: (item) => item.type === '2',
                rules: [
                    { required: true, msg: 'name is required' },
                    {
                        minlength: 6,
                        msg: 'name too short',
                    },
                ],
            },
        ];
        const expected = [
            { id: 'type', ok: true },
            { id: 'name', ok: false, msg: 'name too short' },
        ];

        expect(validate(raw, rules)).to.be.eql(expected);
    });

    it('should return ok for idcard', function () {
        const raw = {
            id: '123456789012345678',
        };
        const rules = [
            {
                id: 'id',
                rules: [{ idcard: true, msg: 'invalid idcard' }],
            },
        ];
        const expected = [{ id: 'id', ok: true }];

        expect(validate(raw, rules)).to.be.eql(expected);
    });

    it('should return ok for idcard X', function () {
        const raw = {
            id: '12345678901234567X',
        };
        const rules = [
            {
                id: 'id',
                rules: [{ idcard: true, msg: 'invalid idcard' }],
            },
        ];
        const expected = [{ id: 'id', ok: true }];

        expect(validate(raw, rules)).to.be.eql(expected);
    });

    it('should return invalid for idcard Y', function () {
        const raw = {
            id: '12345678901234567Y',
        };
        const rules = [
            {
                id: 'id',
                rules: [{ idcard: true, msg: 'invalid idcard' }],
            },
        ];
        const expected = [{ id: 'id', ok: false, msg: 'invalid idcard' }];

        expect(validate(raw, rules)).to.be.eql(expected);
    });

    it('should return invalid for idcard too short', function () {
        const raw = {
            id: '1234567890123456',
        };
        const rules = [
            {
                id: 'id',
                rules: [{ idcard: true, msg: 'invalid idcard' }],
            },
        ];
        const expected = [{ id: 'id', ok: false, msg: 'invalid idcard' }];

        expect(validate(raw, rules)).to.be.eql(expected);
    });

    it('should return invalid for regexp', function () {
        const raw = {
            id: '1+2=3',
        };
        const rules = [
            {
                id: 'id',
                rules: [{ regexp: /^[a-zA-Z0-9]+$/, msg: 'invalid id' }],
            },
        ];
        const expected = [{ id: 'id', ok: false, msg: 'invalid id' }];

        expect(validate(raw, rules)).to.be.eql(expected);
    });

    it('should return ok for regexp', function () {
        const raw = {
            id: '1a2b3',
        };
        const rules = [
            {
                id: 'id',
                rules: [{ regexp: /^[a-zA-Z0-9]+$/, msg: 'invalid id' }],
            },
        ];
        const expected = [{ id: 'id', ok: true }];

        expect(validate(raw, rules)).to.be.eql(expected);
    });

    it('should return invalid for func', function () {
        const raw = {
            person: {
                age: 16,
            },
        };

        const isAdult = (item) => item.age >= 18;
        const rules = [
            {
                id: 'person',
                rules: [{ func: isAdult, msg: 'only for adult' }],
            },
        ];
        const expected = [{ id: 'person', ok: false, msg: 'only for adult' }];

        expect(validate(raw, rules)).to.be.eql(expected);
    });

    it('should return ok for func', function () {
        const raw = {
            person: {
                age: 19,
            },
        };

        const isAdult = (item) => item.age >= 18;
        const rules = [
            {
                id: 'person',
                rules: [{ func: isAdult, msg: 'only for adult' }],
            },
        ];
        const expected = [{ id: 'person', ok: true }];

        expect(validate(raw, rules)).to.be.eql(expected);
    });

    it('should return invalid for combine rules', function () {
        const raw = {
            name: 'Andy',
            age: 18,
        };

        const rules = [
            {
                id: 'name',
                rules: [{ required: true, minlength: 5, msg: 'invalid name' }],
            },
            {
                id: 'age',
                rules: [
                    {
                        required: true,
                        min: 19,
                        max: 20,
                        msg: 'age not matched',
                    },
                ],
            },
        ];
        const expected = [
            { id: 'name', ok: false, msg: 'invalid name' },
            { id: 'age', ok: false, msg: 'age not matched' },
        ];

        expect(validate(raw, rules)).to.be.eql(expected);
    });

    it('should return invalid for combine rules', function () {
        const raw = {
            name: 'Andy Lau',
            age: 21,
        };

        const rules = [
            {
                id: 'name',
                rules: [
                    {
                        required: true,
                        minlength: 5,
                        maxlength: 6,
                        msg: 'invalid name',
                    },
                ],
            },
            {
                id: 'age',
                rules: [
                    {
                        required: true,
                        min: 19,
                        max: 20,
                        msg: 'age not matched',
                    },
                ],
            },
        ];
        const expected = [
            { id: 'name', ok: false, msg: 'invalid name' },
            { id: 'age', ok: false, msg: 'age not matched' },
        ];

        expect(validate(raw, rules)).to.be.eql(expected);
    });

    it('should return ok for combine rules', function () {
        const raw = {
            name: 'Andy W',
            age: 19,
        };

        const rules = [
            {
                id: 'name',
                rules: [
                    {
                        required: true,
                        minlength: 5,
                        maxlength: 6,
                        msg: 'invalid name',
                    },
                ],
            },
            {
                id: 'age',
                rules: [
                    {
                        required: true,
                        min: 19,
                        max: 20,
                        msg: 'age not matched',
                    },
                ],
            },
        ];
        const expected = [
            { id: 'name', ok: true },
            { id: 'age', ok: true },
        ];

        expect(validate(raw, rules)).to.be.eql(expected);
    });

    it('should return the right msg for multiple rules', function () {
        const raw = {
            name: '',
        };

        const rules = [
            {
                id: 'name',
                rules: [
                    { minlength: 1, msg: 'too short' },
                    { minlength: 2, msg: 'at least 2' },
                ],
            },
        ];

        const expected = [{ id: 'name', ok: false, msg: 'too short' }];

        expect(validate(raw, rules)).to.be.eql(expected);
    });

    it('should return the right msg for multiple rules', function () {
        const raw = {
            name: 'a',
        };

        const rules = [
            {
                id: 'name',
                rules: [
                    { minlength: 1, msg: 'too short' },
                    { minlength: 2, msg: 'at least 2' },
                ],
            },
        ];

        const expected = [{ id: 'name', ok: false, msg: 'at least 2' }];

        expect(validate(raw, rules)).to.be.eql(expected);
    });

    it('should return the right msg for multiple rules for Array', function () {
        const raw = {
            list: [{ name: 'Tony' }],
        };

        const rules = [
            {
                id: 'list',
                rules: [
                    { minlength: 1, msg: 'too short' },
                    { minlength: 2, msg: 'at least 2' },
                ],
            },
        ];

        const expected = [{ id: 'list', ok: false, msg: 'at least 2' }];

        expect(validate(raw, rules)).to.be.eql(expected);
    });

    describe('getFirstErrMsg unit test', function () {
        it('should return empty string', function () {
            expect(getFirstErrMsg()).to.be.eql('');
        });

        it('should return invalid bar', function () {
            const results = [
                { id: 'foo', ok: true },
                { id: 'bar', ok: false, msg: 'invalid bar' },
            ];
            expect(getFirstErrMsg(results)).to.be.eql('invalid bar');
        });
    });

    describe('getErrObj unit test', function () {
        it('should return empty object', function () {
            expect(getErrObj()).to.be.eql({});
        });

        it('should return invalid object', function () {
            const results = [
                { id: 'foo', ok: true },
                { id: 'bar', ok: false, msg: 'invalid bar' },
            ];
            expect(getErrObj(results)).to.be.eql({ bar: 'invalid bar' });
        });
    });
});
